﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using CloudShot.Capturers;
using CloudShot.Core.Interfaces;
using CloudShot.Core.Types;
using CloudShot.Core.Utils;
using CloudShot.Entities;
using CloudShot.ImageStorages;
using CloudShot.Interfaces;
using CloudShot.Logic;
using CloudShot.Properties;
using CloudShot.Services;
using CloudShot.Settings;
using CloudShot.Utils;
using DropboxImageStorage;

namespace CloudShot.Forms
{
  public partial class MainForm : Form
  {
    private readonly IApplicationUpdaterService _applicationUpdaterService = new ApplicationUpdaterService();
    private readonly IConfigurationService _configurationService;
    private readonly IConfigurationSection _appConfiguration;
    private readonly IConfigurationSection _recentShotsConfiguration;
    private readonly IClipboardService _clipboardService = new ClipboardService();
    private readonly INotificationService _notificationService;

    private readonly IHotkeyService _hotkeyService;

    private readonly RecentShotsQueue _recentShotsQueue = new RecentShotsQueue();
    private StorageContainer _storageContainer;
    private ToolStripMenuItem _defaultShotMenu;

    private bool _isShottingStarted;

    private static string ProductAndVersion
    {
      get { return Application.ProductName + " " + Application.ProductVersion; }
    }

    public MainForm()
    {
      Icon = Resources.applicationIcon;

      InitializeComponent();

      treeIcon.Icon = Resources.trayIcon;
      treeIcon.Text = ProductAndVersion;

      _hotkeyService = new HotkeyService(this);

      _configurationService = new ConfigurationService();
      _appConfiguration = _configurationService.GetAppConfiguration();
      _recentShotsConfiguration = _configurationService.GetRecentShotsConfiguration();
      _notificationService = new NotificationService(treeIcon);

      InitStorages(_configurationService);
    }

    private void InitStorages(IConfigurationService configurationService)
    {
      var storages = new List<IImageStorage>
      {
        new FileSystemStorage(),
        new DropboxStorage()
      };

      IPluginManager pm = new PluginManager(AppHelper.PluginsPath);

      storages.AddRange(pm.GetPlugins());

      //Load storages settings
      foreach (IImageStorage storage in storages)
      {
        IConfigurationSection pluginConfig = configurationService.GetPluginConfiguration(storage.GetType());
        storage.Init(pluginConfig);
      }

      //Set active storage
      _storageContainer = new StorageContainer(storages, _appConfiguration);
      _storageContainer.ActiveStorageChanged += OnActiveStorageChanged;

      InitStoragesMenu();
    }

    private void InitStoragesMenu()
    {
      if (_storageContainer.Storages.Count() < 2)
        return;

      int index = treeIcon.ContextMenuStrip.Items.IndexOf(mnuSettings);

      if (index == -1)
        return;

      //add storages to menu
      foreach (IImageStorage storage in _storageContainer.Storages)
      {
        var item = new ToolStripMenuItem(storage.Name, storage.Logo);
        item.Tag = storage;
        item.Checked = storage == _storageContainer.ActiveStorage;
        item.Click += OnStorageItemClick;

        treeIcon.ContextMenuStrip.Items.Insert(index++, item);
      }
      treeIcon.ContextMenuStrip.Items.Insert(index, new ToolStripSeparator());
    }

    private void SetupHotkeys(CloudShotSettings cloudShotSettings)
    {
      if (IsDisposed)
        return;

      var errors = new List<string>();
      string error;

      if (!RegisterHotkey(cloudShotSettings.ShotHotkey, mnuShot, OnShot, out error))
        errors.Add(error);

      if (!RegisterHotkey(cloudShotSettings.InstantShotHotkey, mnuInstantShot, OnInstanShotClick, out error))
        errors.Add(error);

      if (!RegisterHotkey(cloudShotSettings.RecordHotkey, mnuRecord, OnRecordVideo, out error))
        errors.Add(error);

      if (errors.Count > 0)
      {
        using (var dialog = new HotkeyOccupiedDialog(errors, SystemIcons.Exclamation))
        {
          if (dialog.ShowDialog() == DialogResult.OK)
            ShowSettings(true); 
        }
      }
    }

    private void SetupDefaultShotAction(CloudShotSettings cloudShotSettings)
    {
      mnuInstantShot.Font = DefaultFont;
      mnuRecord.Font = DefaultFont;
      mnuShot.Font = DefaultFont;

      switch (cloudShotSettings.SystemTrayClick)
      {
        case ShotType.InstantShot:
          _defaultShotMenu = mnuInstantShot;
          break;
        case ShotType.Record:
          _defaultShotMenu = mnuRecord;
          break;
        default:
          _defaultShotMenu = mnuShot;
          break;
      }

      _defaultShotMenu.Font = new Font(DefaultFont, FontStyle.Bold);
    }

    private bool RegisterHotkey(Hotkey hotkey, ToolStripMenuItem menu, EventHandler callback, out string error)
    {
      error = string.Empty;

      // if hotkey is empty, that means we have to set no hotkey and we have to show in menu empty string in hotkey section.
      if (hotkey.IsEmpty)
      {
        menu.ShortcutKeyDisplayString = string.Empty;
        return true;
      }

      if (_hotkeyService.RegisterHotkey(hotkey, callback))
      {
        menu.ShortcutKeyDisplayString = hotkey.ToString();
        return true;
      }

      error = string.Format("{0}: {1}", menu.Text, hotkey.ToString());
      return false;
    }

    private async Task Shot(ShotType shotType)
    {
      if (_isShottingStarted)
        return;

      try
      {
        _isShottingStarted = true;

        Image image;

        switch (shotType)
        {
          case ShotType.InstantShot:
            image = MakeScreenshot(true);
            break;
          case ShotType.Record:
            image = RecordVideo();
            break;
          default:
            image = MakeScreenshot(false);
            break;
        }

        if (image == null)
          return;

        await SaveScreenshot(image, shotType);
      }
      catch (Exception err)
      {
        ErrorsHelper.TraceError(err);
        ErrorsHelper.ShowError(err);
      }
      finally
      {
        _isShottingStarted = false;
      }
    }

    private Image MakeScreenshot(bool isInstantShot)
    {
      var settings = _appConfiguration.GetOrCreateAs<CloudShotSettings>();
      ICapturer capturer = new ShotCapturer(_storageContainer, _appConfiguration, _notificationService, settings.IsCaptureMouseInScreenshots,
                                            settings.IsSoundsEnabled, Color.DarkSlateBlue, isInstantShot);
      return capturer.Capture();
    }

    private Image RecordVideo()
    {
      ICapturer capturer = new RecordCapturer(_storageContainer, _notificationService, Color.DarkSlateGray, _appConfiguration);
      return capturer.Capture();
    }

    private async Task SaveScreenshot(Image image, ShotType shotType)
    {
      IImageStorage activeStorage = _storageContainer.ActiveStorage;

      if (activeStorage == null)
        return;

      _clipboardService.Clear(); //clear old screenshot url from clipboard

      byte[] imageData;
      ImageSaveFormat imageSaveFormat = shotType == ShotType.Record ? ImageSaveFormat.Gif : activeStorage.CurrentImageFormat;

      using (var memoryStream = new MemoryStream())
      {
        image.Save(memoryStream, imageSaveFormat.GetImageFormat());
        memoryStream.Position = 0; // reset stream
        imageData = memoryStream.ToArray();
      }

      string shotName = AppHelper.GenerateShotName(imageSaveFormat.GetImageFormat(), shotType);
      string shotUrl = await activeStorage.SaveImage(imageData, shotName, shotType == ShotType.Record);

      if (shotUrl == null)
      {
        _notificationService.ShowWarning(Resources.Notifications_UploadCancelledTitle, Resources.Notifications_UploadCancelledBody);
        return;
      }

      var cloudShotSettings = _appConfiguration.GetOrCreateAs<CloudShotSettings>();
      var shotInfo = new ShotInfo(shotName, shotUrl, image);

      if (cloudShotSettings.IsOpenInBrowser)
        AppHelper.OpenInViewer(shotInfo.Path);

      AddRecentShot(shotInfo);

      if (!string.IsNullOrEmpty(shotInfo.Path))
        _clipboardService.SetText(shotInfo.Path);

      if (cloudShotSettings.IsShowNotifications) //TODO: maybe will be better to move this check inside NotificationService?
        _notificationService.ShowInfo(shotInfo, () => AppHelper.OpenInViewer(shotInfo.Path));

      image.Dispose();
    }

    private void SetTrayTooltip(CloudShotSettings settings)
    {
      const int maxTrayTextLength = 63;

      var sb = new StringBuilder();

      sb.AppendLine(ProductAndVersion);

      if (!settings.ShotHotkey.IsEmpty)
      {
        sb.AppendFormat(Resources.MainForm_TrayTooltip_Shot, settings.ShotHotkey);
        sb.AppendLine();
      }

      if (_storageContainer.ActiveStorage != null)
        sb.AppendFormat(Resources.MainForm_TrayTooltip_Storage, _storageContainer.ActiveStorage.Name);

      string treeIconText = sb.ToString();

      if (treeIconText.Length > maxTrayTextLength)
        treeIconText = treeIconText.Substring(0, maxTrayTextLength);

      treeIcon.Text = treeIconText;
    }

    private void ShowSettings(bool activateHotkeysTab = false)
    {
      mnuSettings.Enabled = false;
      _hotkeyService.ClearAllHotkeys();

      var settingsForm = new SettingsForm(_storageContainer, _appConfiguration);

      if (activateHotkeysTab)
        settingsForm.ActivateHotkeysTab();

      settingsForm.ShowDialog(this);

      ApplySettingsToUI();
      mnuSettings.Enabled = true;
    }

    private async Task TryUpdateApplication()
    {
      if (!await _applicationUpdaterService.IsUpdateAvailable())
        return;

      if (!await _applicationUpdaterService.InstallUpdate())
        return;
      
      updateTimer.Stop();

      //wait untill all forms will be closed
      while (Application.OpenForms.Count > 1)
      {
        await Task.Delay(AppHelper.ShortUpdateInterval);
      }

      Application.Restart();
    }

    private void SetApplicationUpdateInterval(TimeSpan interval)
    {
      updateTimer.Stop();
      updateTimer.Interval = (int)interval.TotalMilliseconds;
      updateTimer.Start();
    }

    //TODO: rename, I guess we should find better name for this method
    private void ApplySettingsToUI()
    {
      var settings = _appConfiguration.GetOrCreateAs<CloudShotSettings>();
      _configurationService.SetAutomaticStartup(settings.IsStartWithComputer);
      SetupHotkeys(settings);
      SetupDefaultShotAction(settings);
      SetTrayTooltip(settings);
    }

    private void ShowUpdateNotification()
    {
      var settings = _appConfiguration.GetOrCreateAs<CloudShotSettings>();

      if (settings.Version != Application.ProductVersion)
      {
        if (string.IsNullOrEmpty(settings.Version))
        {
          string welcomeMessage;

          if (settings.ShotHotkey.IsEmpty)
            welcomeMessage = Resources.WelcomeMessage;
          else
            welcomeMessage = string.Format(Resources.WelcomeMessageWithHotkey, settings.ShotHotkey.ToString());

          _notificationService.ShowInfo(Resources.WelcomeTitle, welcomeMessage);
        }
        else
        {
          _notificationService.ShowInfo(Resources.UpdateTitle, Resources.UpdateMessage, () => AppHelper.OpenInViewer(AppHelper.WhatIsNewUrl));
        }

        settings.Version = Application.ProductVersion;
        _appConfiguration.SaveAs(settings);
      }
    }

    #region Event Handlers

    private async void OnLoad(object sender, EventArgs e)
    {
      Visible = false;

      ApplySettingsToUI();

      var recentShots = _recentShotsConfiguration.GetAs<List<RecentShotInfo>>() ?? new List<RecentShotInfo>();

      _recentShotsQueue.Load(recentShots.Select(s => new ShotInfo
      {
        Name = s.Name,
        Path = s.Path
      })); //TODO maybe it is better to change method signature

      FillRecentShotsMenu(_recentShotsQueue.ToList());

      treeIcon.Visible = true;

      SetApplicationUpdateInterval(AppHelper.UpdateInterval);
      await TryUpdateApplication();
      ShowUpdateNotification();
    }

    private async void OnShot(object sender, EventArgs e)
    {
      await Shot(ShotType.ShotAndEdit);
    }

    private async void OnInstanShotClick(object sender, EventArgs e)
    {
      await Shot(ShotType.InstantShot);
    }

    private async void OnRecordVideo(object sender, EventArgs e)
    {
      await Shot(ShotType.Record);
    }

    private void OnMenuExitClick(object sender, EventArgs e)
    {
      Application.Exit();
    }

    private void OnMenuSettingsClick(object sender, EventArgs e)
    {
      ShowSettings();
    }

    private void OnMenuAboutClick(object sender, EventArgs e)
    {
      mnuAbout.Enabled = false;

      using (var dialog = new AboutBox())
      {
        dialog.ShowDialog();
      }

      mnuAbout.Enabled = true;
    }

    private async void OnUpdateTimerTick(object sender, EventArgs e)
    {
      await TryUpdateApplication();
    }

    private void OnStorageItemClick(object sender, EventArgs e)
    {
      var item = sender as ToolStripMenuItem;

      if (item == null)
        return;

      var storage = item.Tag as IImageStorage;

      if (storage != null)
      {
        _storageContainer.ActiveStorage = storage;
      }
    }

    private void OnActiveStorageChanged(IImageStorage storage)
    {
      foreach (ToolStripItem item in treeIcon.ContextMenuStrip.Items)
      {
        var menuItem = item as ToolStripMenuItem;
        if (menuItem != null)
          menuItem.Checked = menuItem.Tag == storage;
      }

      var settings = _appConfiguration.GetOrCreateAs<CloudShotSettings>();
      SetTrayTooltip(settings);
    }

    private void OnTreeIconMouseClick(object sender, MouseEventArgs e)
    {
      if (e.Button != MouseButtons.Left)
        return;

      if (_defaultShotMenu != null && _defaultShotMenu.Enabled)
      {
        _defaultShotMenu.PerformClick();
      }
      else
      {
        var form = OwnedForms.FirstOrDefault();
        if (form != null)
          form.Activate();
      }
    }

    #endregion

    #region Recent Shots

    private static void OnRecentShotClick(object sender, EventArgs e)
    {
      var menu = sender as ToolStripMenuItem;

      if (menu == null)
        return;

      var recentShot = menu.Tag as ShotInfo;

      if (recentShot == null)
        return;

      AppHelper.OpenInViewer(recentShot.Path);
    }

    private void FillRecentShotsMenu(IList<ShotInfo> shots)
    {
      mnuRecentShots.DropDownItems.Clear();

      foreach (ShotInfo recentShot in shots)
      {
        var menuItem = new ToolStripMenuItem
        {
          ImageScaling = ToolStripItemImageScaling.None,
          Text = recentShot.Name,
          Tag = recentShot,
          Image = recentShot.Thumbnail
        };

        menuItem.Click += OnRecentShotClick;
        mnuRecentShots.DropDownItems.Insert(0, menuItem);
      }

      mnuRecentShots.Enabled = mnuRecentShots.DropDownItems.Count > 0;
    }

    private void AddRecentShot(ShotInfo shotInfo)
    {
      _recentShotsQueue.Add(shotInfo);
      FillRecentShotsMenu(_recentShotsQueue.ToList());

      List<RecentShotInfo> recentShotInfos = _recentShotsQueue.Select(r => new RecentShotInfo
      {
        Name = r.Name,
        Path = r.Path
      }).ToList();

      _recentShotsConfiguration.SaveAs(recentShotInfos);
    }

    #endregion
  }
}